package com.caishi.lkx.exam.records.oss;

import com.aliyun.oss.model.ListObjectsRequest;
import com.aliyun.oss.model.ObjectListing;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.caishi.lkx.exam.records.*;
import com.caishi.lkx.exam.records.dto.*;
import com.zzw.common.threadpool.SysThreadPool;
import com.zzw.common.exception.BizRuntimeException;
import com.zzw.common.file.OssConfig;
import com.zzw.common.file.OssPlugins;
import com.caishi.lkx.exam.ienum.type.RankType;
import com.caishi.lkx.exam.records.vo.CategoryCollectRecordsDtoItemVo;
import com.caishi.lkx.exam.records.vo.NewRecordsDtoItemVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

@Service
@Slf4j
public class OssFileService implements RecordsGetKeyService, RecordsGetDataService, RecordsSetDataService, RecordsDelService {

    private OssConfig ossConfig;

    @Resource(name = "recordsOssPlugins")
    public OssPlugins recordsOssPlugins0;


    @PostConstruct
    void init() {
        ossConfig = recordsOssPlugins0.ossConfig;
    }

    public OssPlugins getOssPlugins(String userId) {
        return recordsOssPlugins0;
    }

    private String ossSetData(String json, String key, String userId) {
        try {
            ByteArrayInputStream inputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
            return getOssPlugins(userId).streamUploadSync(inputStream, key);
        } catch (Exception e) {
            log.error("oss写入失败,进入重写线程 key={}", key, e);
            SysThreadPool.execute(() -> {
                for (var i = 0; i < 5; i++) {
                    try {
                        Thread.sleep(5000);
                        ByteArrayInputStream inputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
                        getOssPlugins(userId).streamUploadSync(inputStream, key);
                        return;
                    } catch (Exception ignore) {
                    }
                }
            });
            return null;
        }
    }

    private void delOssData(String key, String userId) {
        String path = getOssPlugins(userId).getFilePath(key);
        getOssPlugins(userId).deleteFile(path);
    }

    private String ossFileJson(String key, String userId) {
//        创建客户端连接
        var oss = getOssPlugins(userId).createClient();
        String path = getOssPlugins(userId).getFilePath(key);
        log.info(String.format("oss读取:path=%s", path));
        try {
            var object = oss.getObject(ossConfig.getBucket(), path);
            if (object == null) return "";
            var input = object.getObjectContent();
            try {
                return new String(input.readAllBytes());
            } catch (Exception e) {
                log.error(String.format("oss读取失败:path=%s", path), e);
                throw new BizRuntimeException();
            }
        } catch (Exception ignore) {
            System.out.println(String.format("oss读取失败:path=%s", path));
            ignore.printStackTrace();
            return null;
        }
    }


    //    delData
    @Override
    public void delCategoryCountRecordsResult(String userId, String industryCode, String examId, Long categoryId) {
        delOssData(getCategoryCountRecordsKey(userId, industryCode, examId, categoryId), userId);
    }

    @Override
    public void delQuestionRecords(String userId, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        delOssData(getQuestionRecordsKey(userId, industryCode, examId, categoryId, type, id), userId);
    }

    @Override
    public void delCollectCountRecords(String userId, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        delOssData(getCollectCountRecordsKey(userId, industryCode, examId, categoryId, type, id), userId);
    }

    @Override
    public void delErrorRecords(String userId, String industryCode, String examId, Long categoryId) {
        delOssData(getErrorRecordsKey(userId, industryCode, examId, categoryId), userId);
    }

    @Override
    public void delCollectRecords(String userId, String industryCode, String examId, Long categoryId) {
        delOssData(getCollectRecordsKey(userId, industryCode, examId, categoryId), userId);
    }

    @Override
    public void delErrorInCollectData(String userId, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        delOssData(getErrorInCollectDataKey(userId, industryCode, examId, categoryId, type, id), userId);
    }

    @Override
    public void delCollectInCollectionData(String userId, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        delOssData(getCollectInCollectionDataKey(userId, industryCode, examId, categoryId, type, id), userId);
    }

    @Override
    public void delCategoryCollectData(String userId, String industryCode, String examId, Long categoryId) {
        // 删除最新收藏
        delCollectRecords(userId, industryCode, examId, categoryId);
        // 删除集合收藏集合
        var prefix = getOssPlugins(userId).getFilePath(getCollectInCollectionDataKey(userId, industryCode, examId, categoryId, RecordsCollectType.chapter, "test")
                .replace(String.format("%s_%s.json", RecordsCollectType.chapter, "test"), ""));
        List<String> keys = getPrefixKeys(prefix, userId);
        if (!keys.isEmpty()) getOssPlugins(userId).deleteFiles(keys);
        // 修改集合收藏数量为0
        setCollectCountZero(userId, industryCode, examId, categoryId, RankType.collect);
    }

    @Override
    public void delCategoryErrorData(String userId, String industryCode, String examId, Long categoryId) {
        delCategoryErrorData(userId, industryCode, examId, categoryId, true);
    }

    private void delCategoryErrorData(String userId, String industryCode, String examId, Long categoryId, boolean countChange) {
        // 删除最新错题
        delErrorRecords(userId, industryCode, examId, categoryId);
        // 删除集合错题集合
        var prefix = getOssPlugins(userId).getFilePath(getErrorInCollectDataKey(userId, industryCode, examId, categoryId, RecordsCollectType.chapter, "test")
                .replace(String.format("%s_%s.json", RecordsCollectType.chapter, "test"), ""));

        List<String> keys = getPrefixKeys(prefix, userId);
        if (!keys.isEmpty()) getOssPlugins(userId).deleteFiles(keys);

        //清除做题记录里面的错题
        var questionRecordsPrefix = getOssPlugins(userId).getFilePath(getQuestionRecordsKey( userId, industryCode, examId, categoryId, RecordsCollectType.chapter, "test")
                .replace(String.format("%s_%s.json", RecordsCollectType.chapter, "test"), ""));
        List<String> questionRecordsKeys = getPrefixKeys(questionRecordsPrefix, userId);
        if (!questionRecordsKeys.isEmpty()) {

            questionRecordsKeys.forEach( key -> {
                List<String> delErrorQidss = new ArrayList<>();
                var questionRecordsDto = new QuestionRecordsDto().parse(ossFileJson(key, userId));
                questionRecordsDto.getData().keySet().forEach(qid->{
                    if(!questionRecordsDto.get(qid).isRt())
                        delErrorQidss.add(qid);
                });
                //删除做题记录里面的错题
                if(!delErrorQidss.isEmpty()){
                    delErrorQidss.forEach(questionRecordsDto::remove);
                }
            });

        }
        if (countChange) {
            // 修改集合错题数量为0
            setCollectCountZero(userId, industryCode, examId, categoryId, RankType.error);
        }
    }

    @Override
    public void delCategoryQuestionRecords(String userId, String industryCode, String examId, Long categoryId) {
        // 删除集合做题集合
        var prefix = getOssPlugins(userId).getFilePath(getQuestionRecordsKey(userId, industryCode, examId, categoryId, RecordsCollectType.chapter, "test")
                .replace(String.format("%s_%s.json", RecordsCollectType.chapter, "test"), ""));
        List<String> keys = getPrefixKeys(prefix, userId);
        if (!keys.isEmpty()) {
            getOssPlugins(userId).deleteFiles(keys);
            // 修改集合做题数量为0
            setCollectCountZero(userId, industryCode, examId, categoryId, RankType.all);
        }
        delCategoryCountRecordsResult(userId, industryCode, examId, categoryId);
        delCategoryCollectRecordsData(userId, industryCode, examId, categoryId);
        delCategoryErrorData(userId, industryCode, examId, categoryId, false);
    }

    @Override
    public void delCategoryCollectRecordsData(String userId, String industryCode, String examId, Long categoryId) {
        delOssData(getCategoryCollectRecordsKey(userId, industryCode, examId, categoryId), userId);
    }

    @Override
    public void delCategoryData(String userId, String industryCode, String examId, Long categoryId) {
        var key = getOssPlugins(userId).getFilePath(String.format("/%s/%s/%s/%s", industryCode, examId, categoryId, userId));
        var keys = getPrefixKeys(key, userId);
        if (!keys.isEmpty()) getOssPlugins(userId).deleteFiles(keys);
    }

    @Override
    public void delVideoRecordsKey(String userId, String industryCode, String examId, Long categoryId, String columnId, String chapterId) {
        delOssData(getVideoRecordsKey(userId, industryCode, examId, categoryId, columnId, chapterId), userId);
    }

    @Override
    public void delCountVideoRecordsKey(String userId, String industryCode, String examId, Long categoryId, String columnId, String chapterId) {
        delOssData(getCountVideoRecordsKey(userId, industryCode, examId, categoryId, columnId, chapterId), userId);
    }

    @Override
    public void delCountColumnVideoRecordsKey(String userId, String industryCode, String examId, Long categoryId, String columnId) {
        delOssData(getCountColumnVideoRecordsKey(userId, industryCode, examId, categoryId, columnId), userId);
    }

    public List<String> getPrefixKeys(String prefix, String userId) {
        var oss = getOssPlugins(userId).createClient();
        var bucket = ossConfig.getBucket();
        String nextMarker = null;
        ObjectListing objectListing;
        List<String> keys = new LinkedList<>();
        do {
            objectListing = oss.listObjects(new ListObjectsRequest(bucket).withPrefix(prefix).withMarker(nextMarker).withMaxKeys(1000));
            for (var s : objectListing.getObjectSummaries()) {
                keys.add(s.getKey());
            }
            nextMarker = objectListing.getNextMarker();
        } while (objectListing.isTruncated());
        return keys;
    }

    private void setCollectCountZero(String userId, String industryCode, String examId, Long categoryId, RankType type) {
        var prefix = getOssPlugins(userId).getFilePath(getCollectCountRecordsKey(userId, industryCode, examId, categoryId, RecordsCollectType.chapter, "test")
                .replace(String.format("%s_%s.json", RecordsCollectType.chapter, "test"), ""));
        getPrefixKeys(prefix, userId).parallelStream().forEach(key -> {
            var strs = key.split("_");
            var collectId = strs[strs.length - 1].replace(".json", "");
            var t = RecordsCollectType.valueOf(strs[strs.length - 2]);
            var da = getCollectCountRecordsResult(userId, industryCode, examId, categoryId, t, collectId);
            if (type == RankType.all) {
                da.setDc(0);
                da.setEc(0);
            } else if (type == RankType.error) {
                da.setDc(da.getDc() - da.getEc());
                da.setEc(0);
            } else {
                da.setCc(0);
            }
            setCollectCountRecordsData(userId, da, industryCode, examId, categoryId, t, collectId);
        });
    }

    //    getData

    @Override
    public RecordsCountRecordDto getCategoryCountRecordsResult(String userId, String industryCode, String examId, Long categoryId) {
        return new RecordsCountRecordDto().parse(ossFileJson(getCategoryCountRecordsKey(userId, industryCode, examId, categoryId), userId));
    }

    @Override
    public QuestionRecordsDto getQuestionRecordsResult(String userId, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        return new QuestionRecordsDto().parse(ossFileJson(getQuestionRecordsKey(userId, industryCode, examId, categoryId, type, id), userId));
    }

    @Override
    public RecordsCountRecordDto getCollectCountRecordsResult(String userId, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        return new RecordsCountRecordDto().parse(ossFileJson(getCollectCountRecordsKey(userId, industryCode, examId, categoryId, type, id), userId));
    }

    @Override
    public Page<NewRecordsDtoItemVo> getErrorRecordsResult(String userId, String industryCode, String examId, Long categoryId, int page, int size) {
        return ossMapDtoPage(getErrorRecordsResult(userId, industryCode, examId, categoryId), page, size);
    }

    @Override
    public NewQuestionRecordsDto getErrorRecordsResult(String userId, String industryCode, String examId, Long categoryId) {
        return new NewQuestionRecordsDto().parse(ossFileJson(getErrorRecordsKey(userId, industryCode, examId, categoryId), userId));
    }

    @Override
    public Page<NewRecordsDtoItemVo> getCollectRecordsResult(String userId, String industryCode, String examId, Long categoryId, int page, int size) {
        return ossMapDtoPage(getCollectRecordsResult(userId, industryCode, examId, categoryId), page, size);
    }

    @Override
    public NewQuestionRecordsDto getCollectRecordsResult(String userId, String industryCode, String examId, Long categoryId) {
        return new NewQuestionRecordsDto().parse(ossFileJson(getCollectRecordsKey(userId, industryCode, examId, categoryId), userId));
    }

    @Override
    public CollectionErrorAndCollectRecordDataDto getErrorInCollectDataResult(String userId, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        return new CollectionErrorAndCollectRecordDataDto().parse(ossFileJson(getErrorInCollectDataKey(userId, industryCode, examId, categoryId, type, id), userId));
    }

    @Override
    public CollectionErrorAndCollectRecordDataDto getCollectInCollectDataResult(String userId, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        return new CollectionErrorAndCollectRecordDataDto().parse(ossFileJson(getCollectInCollectionDataKey(userId, industryCode, examId, categoryId, type, id), userId));
    }

    @Override
    public Page<CategoryCollectRecordsDtoItemVo> getCategoryCollectRecordsResult(String userId, String industryCode, String examId, Long categoryId, int page, int size) {
        return ossMapDtoPage(
                getCategoryCollectRecordsResult(userId, industryCode, examId, categoryId),
                data -> data.keySet().stream()
                        .map(key -> {
                            var vo = data.getObject(key, CategoryCollectRecordsDtoItemVo.class);
                            var ks = key.split("_");
                            vo.setType(RecordsCollectType.valueOf(ks[0]));
                            vo.setId(ks[1]);
                            return vo;
                        })
                        .sorted((v, v1) -> Long.compare(v1.getT(), v.getT()))
                , page, size);
    }

    @Override
    public CategoryCollectRecordsDto getCategoryCollectRecordsResult(String userId, String industryCode, String examId, Long categoryId) {
        return new CategoryCollectRecordsDto().parse(ossFileJson(getCategoryCollectRecordsKey(userId, industryCode, examId, categoryId), userId));
    }

    @Override
    public VideoRecordsDto getVideoRecordsData(String userId, String industryCode, String examId, Long categoryId, String columnId, String chapterId) {
        return  new VideoRecordsDto().parse(ossFileJson(getVideoRecordsKey(userId, industryCode, examId, categoryId, columnId, chapterId), userId));
    }

    @Override
    public CountVideoRecordsDto getCountVideoRecordsData(String userId, String industryCode, String examId, Long categoryId, String columnId, String chapterId) {
        return new CountVideoRecordsDto().parse(ossFileJson(getCountVideoRecordsKey(userId, industryCode, examId, categoryId, columnId, chapterId), userId));
    }

    @Override
    public CountVideoRecordsDto getCountColumnVideoRecordsData(String userId, String industryCode, String examId, Long categoryId, String columnId) {
        return  new CountVideoRecordsDto().parse(ossFileJson(getCountColumnVideoRecordsKey(userId, industryCode, examId, categoryId, columnId), userId));
    }


    //    getKey
    @Override
    public String getCategoryCountRecordsKey(String userId, String industryCode, String examId, Long categoryId) {
        return String.format("/%s/%s/%s/%s/category.json", industryCode, examId, categoryId, userId);
    }

    @Override
    public String getQuestionRecordsKey(String userId, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        return String.format("/%s/%s/%s/%s/question_records_%s_%s.json", industryCode, examId, categoryId, userId, type, id);
    }

    @Override
    public String getCollectCountRecordsKey(String userId, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        return String.format("/%s/%s/%s/%s/count_records_%s_%s.json", industryCode, examId, categoryId, userId, type, id);
    }

    @Override
    public String getErrorRecordsKey(String userId, String industryCode, String examId, Long categoryId) {
        return String.format("/%s/%s/%s/%s/new_error.json", industryCode, examId, categoryId, userId);
    }

    @Override
    public String getCollectRecordsKey(String userId, String industryCode, String examId, Long categoryId) {
        return String.format("/%s/%s/%s/%s/new_collect.json", industryCode, examId, categoryId, userId);
    }

    @Override
    public String getErrorInCollectDataKey(String userId, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        return String.format("/%s/%s/%s/%s/collect_error_%s_%s.json", industryCode, examId, categoryId, userId, type, id);
    }

    @Override
    public String getCollectInCollectionDataKey(String userId, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        return String.format("/%s/%s/%s/%s/collection_collect_%s_%s.json", industryCode, examId, categoryId, userId, type, id);
    }

    @Override
    public String getCategoryCollectRecordsKey(String userId, String industryCode, String examId, Long categoryId) {
        return String.format("/%s/%s/%s/%s/collect_records.json", industryCode, examId, categoryId, userId);
    }

    @Override
    public String getVideoRecordsKey(String userId, String industryCode, String examId, Long categoryId, String columnId, String chapterId) {
        return String.format("/%s/%s/%s/%s/%s/video_records_%s.json", industryCode, examId, categoryId,columnId, userId, chapterId);
    }

    @Override
    public String getCountVideoRecordsKey(String userId, String industryCode, String examId, Long categoryId, String columnId, String chapterId) {
        return String.format("/%s/%s/%s/%s/%s/count_video_records_%s.json", industryCode, examId, categoryId,columnId, userId, chapterId);
    }

    @Override
    public String getCountColumnVideoRecordsKey(String userId, String industryCode, String examId, Long categoryId, String columnId) {
        return  String.format("/%s/%s/%s/%s/%s/count_column_video_records.json", industryCode, examId, categoryId,columnId, userId);
    }

    //    setData
    @Override
    public String setCategoryCountRecordsData(String userId, RecordsCountRecordDto data, String industryCode, String examId, Long categoryId) {
        if (data.empty()) {
            delCategoryCountRecordsResult(userId, industryCode, examId, categoryId);
            return "";
        }
        return ossSetData(data.toJson(), getCategoryCountRecordsKey(userId, industryCode, examId, categoryId), userId);
    }

    @Override
    public String setQuestionRecordsData(String userId, QuestionRecordsDto data, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        if (data.empty()) {
            delQuestionRecords(userId, industryCode, examId, categoryId, type, id);
            return "";
        }
        return ossSetData(data.toJson(), getQuestionRecordsKey(userId, industryCode, examId, categoryId, type, id), userId);
    }

    @Override
    public String setCollectCountRecordsData(String userId, RecordsCountRecordDto data, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        if (data.empty()) {
            delCollectCountRecords(userId, industryCode, examId, categoryId, type, id);
            return "";
        }
        return ossSetData(data.toJson(), getCollectCountRecordsKey(userId, industryCode, examId, categoryId, type, id), userId);
    }

    @Override
    public String setErrorRecordsData(String userId, NewQuestionRecordsDto data, String industryCode, String examId, Long categoryId) {
        if (data.empty()) {
            delErrorRecords(userId, industryCode, examId, categoryId);
            return "";
        }
        return ossSetData(data.toJson(), getErrorRecordsKey(userId, industryCode, examId, categoryId), userId);
    }

    @Override
    public String setCollectRecordsData(String userId, NewQuestionRecordsDto data, String industryCode, String examId, Long categoryId) {
        if (data.empty()) {
            delCollectRecords(userId, industryCode, examId, categoryId);
            return "";
        }
        return ossSetData(data.toJson(), getCollectRecordsKey(userId, industryCode, examId, categoryId), userId);
    }

    @Override
    public String setErrorInCollectData(String userId, CollectionErrorAndCollectRecordDataDto data, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        if (data.empty()) {
            delErrorInCollectData(userId, industryCode, examId, categoryId, type, id);
            return "";
        }
        return ossSetData(data.toJson(), getErrorInCollectDataKey(userId, industryCode, examId, categoryId, type, id), userId);
    }

    @Override
    public String setCollectInCollectionData(String userId, CollectionErrorAndCollectRecordDataDto data, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id) {
        if (data.empty()) {
            delCollectInCollectionData(userId, industryCode, examId, categoryId, type, id);
            return "";
        }
        return ossSetData(data.toJson(), getCollectInCollectionDataKey(userId, industryCode, examId, categoryId, type, id), userId);
    }

    @Override
    public String setCategoryCollectRecordsData(String userId, CategoryCollectRecordsDto data, String industryCode, String examId, Long categoryId) {
        if (data.empty()) {
            delCategoryCollectRecordsData(userId, industryCode, examId, categoryId);
            return "";
        }
        return ossSetData(data.toJson(), getCategoryCollectRecordsKey(userId, industryCode, examId, categoryId), userId);
    }

    @Override
    public String setVideoRecordsData(String userId, VideoRecordsDto data , String industryCode, String examId, Long categoryId, String columnId, String chapterId) {


        return ossSetData(data.toJson(), getVideoRecordsKey(userId, industryCode, examId, categoryId, columnId, chapterId), userId);
    }

    @Override
    public String setCountVideoRecordsData(String userId, CountVideoRecordsDto data, String industryCode, String examId, Long categoryId, String columnId, String chapterId) {
        if (data.getVn() == 0) {
            delCountVideoRecordsKey(userId, industryCode, examId, categoryId, columnId, chapterId);
            return "";
        }
        return ossSetData(data.toJson(), getCountVideoRecordsKey(userId, industryCode, examId, categoryId, columnId, chapterId), userId);
    }

    @Override
    public String setCountColumnVideoRecordsData(String userId, CountVideoRecordsDto data, String industryCode, String examId, Long categoryId, String columnId) {
        if (data.getVn() == 0) {
            delCountColumnVideoRecordsKey(userId, industryCode, examId, categoryId, columnId);
            return "";
        }
        return ossSetData(data.toJson(), getCountColumnVideoRecordsKey(userId, industryCode, examId, categoryId, columnId), userId);
    }

}
